﻿using System;
using System.Collections.Generic;
using System.Linq;
using Entidades;
using Controladores;
using Common;
using Web.Helpers.Documents.Spreadsheets;
using Web.Pages;

namespace Web.Helpers
{
    public class FacturacionHelper
    {
        public class InfoCalculoPago
        {
            public int CantidadCanciones{ get; set; }
            public int CantidadCoros { get; set; }
            public int CantidadPalabras { get; set; }
            public int CantidadLoops { get; set; }
            public decimal ImporteCanciones { get; set; }
            public decimal ImporteLoops { get; set; }
            public decimal ImporteTotal { get; set; }
            public int IdGuion { get; set; }
            public int IdUsuario { get; set; }
            public int IdTipoUsuario { get; set; }
            public DateTime Fecha { get; set; }
        }

        public static decimal ObtenerValorPorTipoUsuario(GuionCosto costo, int idTipoUsuario)
        {
            decimal valorCosto = 0;
            switch (idTipoUsuario)
            {
                case WellKnownKeys.Entities.TiposUsuario.ACTOR.Id:
                    //valorCosto = costo.Actor;
                    break;
                case WellKnownKeys.Entities.TiposUsuario.ADAPTADOR.Id:
                    valorCosto = costo.Adaptacion;
                    break;
                case WellKnownKeys.Entities.TiposUsuario.MANAGER.Id:
                case WellKnownKeys.Entities.TiposUsuario.PRODUCTOR.Id:
                    //valorCosto = costo.Manager;
                    break;
                case WellKnownKeys.Entities.TiposUsuario.DIRECTOR.Id:
                    valorCosto = costo.Direccion;
                    break;
                case WellKnownKeys.Entities.TiposUsuario.EDITOR.Id:
                    //valorCosto = costo.Editor;
                    break;
                case WellKnownKeys.Entities.TiposUsuario.MEZCLADOR.Id:
                    valorCosto = costo.Mezcla;
                    break;
                case WellKnownKeys.Entities.TiposUsuario.OPERADOR.Id:
                    //valorCosto = costo.Operador;
                    break;
                case WellKnownKeys.Entities.TiposUsuario.TRADUCTOR.Id:
                    valorCosto = costo.Traduccion;
                    break;
            }
            return valorCosto;
        }
        //public static DateTime CalcularFechaDePago(DateTime fecha)
        //{
        //    //Obtener el 1° del mes siguiente, y sumarle 45 días..
        //    return new DateTime(fecha.Year, fecha.Month, 1).AddMonths(1).AddDays(44); //sumo 44 al día 1 (sino, quedaría en el 16vo día, y no el 15vo..)
        //}
        public static object ObtenerFechaPago(int idEstadoPago, DateTime fecha, DateTime? fechaPago)
        {
            object _fecha = null;
            switch (idEstadoPago)
            {
                case WellKnownKeys.Entities.EstadosPago.PENDIENTE_DE_PAGO.Id:
                    //_fecha = FacturacionHelper.CalcularFechaDePago(fecha);
                    _fecha = fecha;
                    break;
                case WellKnownKeys.Entities.EstadosPago.PAGADO.Id:
                    _fecha = fechaPago.HasValue ? fechaPago.Value : new DateTime?();
                    break;
            }
            return _fecha;
        }

        public static InfoCalculoPago CalcularPagoNoActores(Guion guion, int idUsuario, int idTipoUsuario, int idCategoriaUsuario)
        {
            var importe = 0m;

            var generarPago = true;

            var infoCalculoPago = default(InfoCalculoPago);

            if (idCategoriaUsuario == WellKnownKeys.Entities.CategoriasUsuario.FREELANCE.Id)
            {
                switch (guion.Proyecto.Tipo.Id)
                {
                    case WellKnownKeys.Entities.TiposProyecto.SERIE.Id:
                        importe = FacturacionHelper.ObtenerValorPorTipoUsuario(guion.Costo, idTipoUsuario) * guion.Loops;
                        break;
                    case WellKnownKeys.Entities.TiposProyecto.VIDEOJUEGO.Id:
                        switch (idTipoUsuario)
                        {
                            case WellKnownKeys.Entities.TiposUsuario.ADAPTADOR.Id:
                            //case WellKnownKeys.Entities.TiposUsuario.MANAGER.Id:
                            case WellKnownKeys.Entities.TiposUsuario.DIRECTOR.Id:
                            case WellKnownKeys.Entities.TiposUsuario.EDITOR.Id:
                            case WellKnownKeys.Entities.TiposUsuario.MEZCLADOR.Id:
                            case WellKnownKeys.Entities.TiposUsuario.OPERADOR.Id:
                                generarPago = false;
                                break;
                            case WellKnownKeys.Entities.TiposUsuario.TRADUCTOR.Id:
                                importe = guion.Costo.Traduccion * guion.Loops;
                                break;
                        }
                        break;
                }

            }

            if (idCategoriaUsuario == WellKnownKeys.Entities.CategoriasUsuario.CONTRATADO.Id)
            {
                importe = 0;
            }

            if (generarPago)
            {
                infoCalculoPago = new InfoCalculoPago
                {
                    Fecha               = guion.FechaCreacion,
                    IdGuion             = guion.Id,
                    IdUsuario           = idUsuario,
                    IdTipoUsuario       = idTipoUsuario,
                    CantidadLoops       = guion.Loops,
                    CantidadCanciones   = 0,
                    CantidadCoros       = 0,
                    CantidadPalabras    = 0,
                    ImporteCanciones    = 0,
                    ImporteLoops        = importe,
                    ImporteTotal        = importe
                };
            }

            return infoCalculoPago;
        }
        public static InfoCalculoPago CalcularPagoActores(ProyectoTipo tipoProyecto, GuionCosto costo, List<GuionItem> guionItemsDelActor)
        {
            var guion = guionItemsDelActor.FirstOrDefault();
            if (guion == null)
                return null;

            var infoCalculoPago = new InfoCalculoPago
            {
                Fecha               = guion.FechaGrabacion.HasValue ? guion.FechaGrabacion.Value : guion.Guion.FechaCreacion,
                IdGuion             = guion.Guion.Id,
                IdUsuario           = guion.Actor.Id,
                IdTipoUsuario       = guion.Actor.Tipo.Id,
                CantidadCanciones   = guionItemsDelActor.Count(x => x.TipoCancion.IsNotNull() && x.TipoCancion.Id == WellKnownKeys.Entities.TiposCancion.CANCION.Id),
                CantidadCoros       = guionItemsDelActor.Count(x => x.TipoCancion.IsNotNull() && x.TipoCancion.Id == WellKnownKeys.Entities.TiposCancion.CORO.Id),
                CantidadPalabras    = guionItemsDelActor.Count(x => x.TipoCancion.IsNotNull() && x.TipoCancion.Id == WellKnownKeys.Entities.TiposCancion.PALABRAS.Id),
                CantidadLoops       = guionItemsDelActor.Sum(x => x.Loops)
            };

            const int cantidadMinimaLoops = 10;

            if (guion.Actor.Categoria.Id == WellKnownKeys.Entities.CategoriasUsuario.FREELANCE.Id)
            {
                switch (tipoProyecto.Id)
                {
                    case WellKnownKeys.Entities.TiposProyecto.SERIE.Id:
                        infoCalculoPago.ImporteLoops = infoCalculoPago.CantidadLoops <= cantidadMinimaLoops ? costo.LoopMinimo : costo.LoopMinimo + (costo.Loops * (infoCalculoPago.CantidadLoops - cantidadMinimaLoops ) );
                        break;
                    case WellKnownKeys.Entities.TiposProyecto.VIDEOJUEGO.Id:
                        infoCalculoPago.ImporteLoops = costo.LoopMinimo + (costo.Loops * infoCalculoPago.CantidadLoops);
                        break;
                }

                infoCalculoPago.ImporteCanciones = ((costo.Cancion*infoCalculoPago.CantidadCanciones) +
                                                    (costo.Coro*infoCalculoPago.CantidadCoros) +
                                                    (costo.Palabras*infoCalculoPago.CantidadPalabras));

                infoCalculoPago.ImporteTotal = infoCalculoPago.ImporteCanciones + infoCalculoPago.ImporteLoops;                
            }

            if (guion.Actor.Categoria.Id == WellKnownKeys.Entities.CategoriasUsuario.CONTRATADO.Id)
            {
                infoCalculoPago.ImporteLoops = 0;
                infoCalculoPago.ImporteCanciones = 0;
                infoCalculoPago.ImporteTotal = 0;
            }

            return infoCalculoPago;
        }
        public static InfoCalculoPago CalcularPagoTemporizador(Guion guion, int idUsuario, int idTipoUsuario, int idCategoriaUsuario)
        {
            var importe = 0m;

            var infoCalculoPago = default(InfoCalculoPago);

            var costoTemporizacion = guion.Costo.Temporizacion;

            var cantidadLoopsSubtitulados = 0;

            var personajesSubtitulados = guion.Items.Where(x => x.Subtitulado);
            if (personajesSubtitulados.Any())
                cantidadLoopsSubtitulados = personajesSubtitulados.Sum(x => x.Loops);

            if (idCategoriaUsuario == WellKnownKeys.Entities.CategoriasUsuario.FREELANCE.Id)
            {
                switch (guion.Proyecto.Tipo.Id)
                {
                    case WellKnownKeys.Entities.TiposProyecto.SERIE.Id:
                        importe = costoTemporizacion * cantidadLoopsSubtitulados;
                        break;
                    case WellKnownKeys.Entities.TiposProyecto.VIDEOJUEGO.Id:
                        break;
                }
            }
            if (idCategoriaUsuario == WellKnownKeys.Entities.CategoriasUsuario.CONTRATADO.Id)
            {
                importe = 0;
            }

            infoCalculoPago = new InfoCalculoPago
            {
                Fecha               = guion.FechaCreacion,
                IdGuion             = guion.Id,
                IdUsuario           = idUsuario,
                IdTipoUsuario       = idTipoUsuario,
                CantidadLoops       = cantidadLoopsSubtitulados,
                CantidadCanciones   = 0,
                CantidadCoros       = 0,
                CantidadPalabras    = 0,
                ImporteCanciones    = 0,
                ImporteLoops        = importe,
                ImporteTotal        = importe
            };
            
            return infoCalculoPago;
        }
        
        /// <summary>
        /// Devuelve la instancia de un Pago existente (en base a los parámetros), o genera una nueva si no existe
        /// </summary>
        /// <param name="controladorBase"></param>
        /// <param name="infoCalculoPago"></param>
        /// <param name="estadoPago"></param>
        /// <param name="fechaPago"></param>
        /// <param name="medioPago"></param>
        /// <returns></returns>
        public static Pago ObtenerPago(ControladorBase<Pago> controladorBase, InfoCalculoPago infoCalculoPago, PagoEstado estadoPago, DateTime? fechaPago = null, PagoMedio medioPago = null)
        {
            var pago = default(Pago);

                pago = new PagoControlador().ObtenerPago(infoCalculoPago.IdUsuario, infoCalculoPago.IdTipoUsuario, infoCalculoPago.IdGuion, estadoPago.Id);
                pago = pago.IsNotNull()
                    ? controladorBase.ObtenerEntidad(pago.Id)
                    : new Pago() { Guion = new ControladorBase<Guion>().ObtenerEntidad(infoCalculoPago.IdGuion) };

                //pago.Id               = -1;
                pago.Activo             = true;
                pago.Fecha              = infoCalculoPago.Fecha;
                pago.Loops              = infoCalculoPago.CantidadLoops;
                pago.ImporteLoops       = infoCalculoPago.ImporteLoops;
                pago.Canciones          = infoCalculoPago.CantidadCanciones + infoCalculoPago.CantidadCoros + infoCalculoPago.CantidadPalabras;
                pago.ImporteCanciones   = infoCalculoPago.ImporteCanciones;
                pago.ImporteTotal       = infoCalculoPago.ImporteTotal;
                pago.Proveedor          = new ControladorBase<Usuario>().ObtenerEntidad( infoCalculoPago.IdUsuario );
                pago.ProveedorRol       = new ControladorBase<UsuarioTipo>().ObtenerEntidad(x => x.Id == infoCalculoPago.IdTipoUsuario);
                pago.Estado             = pago.Estado ?? estadoPago;
                pago.FechaPago          = pago.FechaPago.HasValue ? pago.FechaPago.Value : fechaPago; 
                pago.MedioPago          = pago.MedioPago ?? medioPago;

            return pago;
        }
        
        /// <summary>
        /// Actualiza los Pagos marcandolos "Pagados", y la correspondiente info.
        /// Además, envía notificaciones por Email y SMS.
        /// </summary>
        /// <param name="idsPago"></param>
        /// <param name="idMedioPago"></param>
        /// <returns></returns>
        public static Dictionary<int, bool> RealizarPagos(int[] idsPago, int idMedioPago)
        {
            var pagosActualizados = new Dictionary<int, bool>();
            try
            {
                var controladorPago         = new ControladorBase<Pago>();
                var controladorPagoEstado   = new ControladorBase<PagoEstado>();
                var controladorPagoMedio    = new ControladorBase<PagoMedio>();

                var medioPago   = controladorPagoMedio.ObtenerEntidad(idMedioPago);
                var estadoPago  = controladorPagoEstado.ObtenerEntidad(WellKnownKeys.Entities.EstadosPago.PAGADO.Id);
                var fechaPago   = DateTime.Now;

                var currentUser = SessionHelper.GetCurrentSessionInfo().User;

                #region Actualización de Pagos
                foreach (var idPago in idsPago)
                {
                    #region Por cada Pago
                    try
                    {
                        var isPagoActualizado = false;

                        var pago = controladorPago.ObtenerEntidad(idPago);
                        if (pago.IsNotNull())
                        {
                            pago.Estado     = estadoPago;
                            pago.FechaPago  = fechaPago;
                            pago.MedioPago  = medioPago;
                            pago.UsuarioUltimaModificacion = new ControladorBase<Usuario>().ObtenerEntidad( currentUser.Id );
                            pago.FechaUltimaModificacion = DateTime.Now;

                            isPagoActualizado = controladorPago.GuardarEntidad(pago);
                        }
                        
                        if ( !pagosActualizados.ContainsKey( idPago ) )
                            pagosActualizados.Add( pago.Id, isPagoActualizado );

                        // TODO => Quizás este método sólo debería persistir los cambios en cada Pago, y no enviar las notificaciones (Email/SMS).
                        // Rompe con el concepto de SRP del SOLID
                        var mailEnviado = MailHelper.EnviarMailNotificacionGuionPagado(pago);
                        var smsEnviado  = MailHelper.EnviarSmsNotificacionGuionPagado(pago);
                    }
                    catch (Exception ex)
                    {
                        BasePage.LogError(ex);
                        try
                        {
                            if ( !pagosActualizados.ContainsKey( idPago ) )
                                pagosActualizados.Add( idPago, false );
                        }
                        catch (Exception ex2)
                        {
                            BasePage.LogError(ex2);
                            throw;
                        }
                    }
                    #endregion
                }
                #endregion
            }
            catch (Exception ex)
            {
                BasePage.LogError(ex);
                throw;
            }
            return pagosActualizados;
        }

        /// <summary>
        /// Actualiza todos los Pagos de un Guión (pudiendo agregar, editar y eliminar)
        /// </summary>
        /// <param name="guion"></param>
        public static void ActualizarPagosDelGuion(Guion guion)
        {
            try
            {
                var currentUser = SessionHelper.GetCurrentSessionInfo().User;

                var controladorPagos = new ControladorBase<Pago>();
                var controladorReclamos = new ControladorBase<Reclamo>();

                var usuarios = new Dictionary<int, int>();

                var estadoPagoPendienteDePago = new ControladorBase<PagoEstado>().ObtenerEntidad(x => x.Id == WellKnownKeys.Entities.EstadosPago.PENDIENTE_DE_PAGO.Id);

                var idsPagosAEliminar = controladorPagos.ObtenerEntidades(x => x.Guion.Id == guion.Id).Select(x => x.Id).ToList();

                #region Tipos de Usuario
                if ( guion.Items.Count > 0 )
                    usuarios.Add( WellKnownKeys.Entities.TiposUsuario.ACTOR.Id, WellKnownKeys.Entities.DEFAULT_ID );
                if ( guion.Adaptador != null )
                    usuarios.Add(WellKnownKeys.Entities.TiposUsuario.ADAPTADOR.Id, guion.Adaptador.Id);
                if ( guion.Director != null )
                    usuarios.Add(WellKnownKeys.Entities.TiposUsuario.DIRECTOR.Id, guion.Director.Id);
                if ( guion.Mezclador != null )
                    usuarios.Add(WellKnownKeys.Entities.TiposUsuario.MEZCLADOR.Id, guion.Mezclador.Id);
                if ( guion.Traductor != null )
                    usuarios.Add(WellKnownKeys.Entities.TiposUsuario.TRADUCTOR.Id, guion.Traductor.Id);
                if ( guion.Temporizador != null )
                    usuarios.Add(WellKnownKeys.Entities.TiposUsuario.TEMPORIZADOR.Id, guion.Temporizador.Id);
                #endregion

                foreach (KeyValuePair<int, int> usuarioItem in usuarios)
                {                    
                    var idTipoUsuario       = usuarioItem.Key;
                    var idUsuario           = usuarioItem.Value;
                    var usuario             = default(Usuario);
                    var pago                = default(Pago);
                    switch (idTipoUsuario)
                    {
                        case WellKnownKeys.Entities.TiposUsuario.ACTOR.Id:
                            #region Cálculo del Pago
                            if ( guion.Items == null )
                                break;
                            var itemsPorActor = guion.Items.Where(x => x.Actor != null && x.FechaGrabacion.HasValue && x.Subtitulado == false).GroupBy(x => x.Actor.Id).ToList();
                            if ( itemsPorActor.IsNull() )
                                break;
                            foreach (var guionItems in itemsPorActor) //Items de Actores que participaron en el Guion, agrupados
                            {
                                var infoCalculoPagoActores = CalcularPagoActores(guion.Proyecto.Tipo, guion.Costo, guionItems.ToList());
                                pago = GuardarPago(infoCalculoPagoActores, estadoPagoPendienteDePago, controladorPagos);
                                ActualizarListaPagosAEliminar(idsPagosAEliminar, pago);
                            }
                            #endregion
                            break;
                        case WellKnownKeys.Entities.TiposUsuario.ADAPTADOR.Id:
                        case WellKnownKeys.Entities.TiposUsuario.DIRECTOR.Id:
                        case WellKnownKeys.Entities.TiposUsuario.MEZCLADOR.Id:
                        case WellKnownKeys.Entities.TiposUsuario.TRADUCTOR.Id:
                            usuario = guion.ObtenerUsuario(idTipoUsuario);
                            if (usuario == null)
                                continue;
                            #region Cálculo del Pago
                            //if ( guion.Estado.Nombre.ToUpper() == WellKnownKeys.Entities.EstadosGuion.FINALIZADO )
                            //{
                                var infoCalculoPagoNoActores = CalcularPagoNoActores(guion, idUsuario, idTipoUsuario, usuario.Categoria.Id);
                                pago = GuardarPago(infoCalculoPagoNoActores, estadoPagoPendienteDePago, controladorPagos);
                                ActualizarListaPagosAEliminar(idsPagosAEliminar, pago);
                            //}
                            #endregion
                            break;
                        case WellKnownKeys.Entities.TiposUsuario.TEMPORIZADOR.Id:
                            usuario = guion.ObtenerUsuario(idTipoUsuario);
                            if (usuario == null)
                                continue;
                            #region Cálculo del Pago
                            var infoCalculoPagoTemporizador = CalcularPagoTemporizador(guion, idUsuario, idTipoUsuario, usuario.Categoria.Id);
                            pago = GuardarPago(infoCalculoPagoTemporizador, estadoPagoPendienteDePago, controladorPagos);
                            ActualizarListaPagosAEliminar(idsPagosAEliminar, pago);
                            #endregion
                            break;
                    }
                }

                // Eliminar Pagos obsoletos/desafectados
                foreach (var idPagoAEliminar in idsPagosAEliminar)
                {
                    // Si el Pago a eliminar tiene al menos 1 Reclamos asociado, entonces dar de baja al Pago, pero no eliminarlo..
                    var reclamos = controladorReclamos.ObtenerEntidades(x => x.Pago.Id == idPagoAEliminar);
                    if (reclamos != null && reclamos.Any())
                    {
                        var pago = controladorPagos.ObtenerEntidad(idPagoAEliminar);
                        if (pago != null)
                        {
                            pago.Activo = false;
                            pago.UsuarioUltimaModificacion = new ControladorBase<Usuario>().ObtenerEntidad(currentUser.Id);
                            pago.FechaUltimaModificacion = DateTime.Now;
                            controladorPagos.GuardarEntidad(pago);
                        }
                    }
                    else
                    {
                        // Si el Pago a eliminar no tiene ningún Reclamo asociado, directamente eliminarlo..
                        controladorPagos.EliminarEntidad(idPagoAEliminar);
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        #region Private Methods
        private static void ActualizarListaPagosAEliminar(List<int> idsPagosAEliminar, Pago pago)
        {
            if (pago.IsNotNull())
            {
                idsPagosAEliminar.Remove(pago.Id);
            }            
        }
        
        /// <summary>
        /// Guarda los datos de un Pago (existente o nuevo)
        /// </summary>
        /// <param name="infoCalculoPago"></param>
        /// <param name="estadoPago"></param>
        /// <param name="controlador"></param>
        /// <returns></returns>
        private static Pago GuardarPago(InfoCalculoPago infoCalculoPago, PagoEstado estadoPago, ControladorBase<Pago> controlador)
        {
            var currentUser = SessionHelper.GetCurrentSessionInfo().User;

            var pago = default(Pago);
            if (infoCalculoPago == null)
                return null;
            
            pago = ObtenerPago(controlador, infoCalculoPago, estadoPago, null, null);
            if (pago == null)
                return null;

            pago.UsuarioUltimaModificacion = new ControladorBase<Usuario>().ObtenerEntidad(currentUser.Id);
            pago.FechaUltimaModificacion = DateTime.Now;

            controlador.GuardarEntidad(pago);
            
            return pago;
        }
        #endregion
    }
}